#include "sock.h"
#include "log/Logger.h"
#include "net/endian.h"
#include "utility/assertion.h"
#include "wrap/socket.h"

#include <cstring>
#include <errno.h>

namespace _ {

template<typename To, typename From>
const To *const_static_const(const From *t) {
    return static_cast<const To *>(static_cast<const void *>(t));
}

}

struct sockaddr *Sock::sockaddr_cast(struct sockaddr_in *addr) {
    return static_cast<sockaddr *>(static_cast<void *>(addr));
}

const struct sockaddr *Sock::sockaddr_cast(const struct sockaddr_in *addr) {
    return _::const_static_const<struct sockaddr>(addr);
}

const struct sockaddr_in *Sock::sockaddr_in_cast(const struct sockaddr *addr) {
    return _::const_static_const<sockaddr_in>(addr);
}

int Sock::accept(struct sockaddr_in &addr, int fd) {
    auto length = static_cast<socklen_t>(sizeof(addr));
    const auto fdCon = Wrap::accept4_(fd, sockaddr_cast(&addr), &length, SOCK_NONBLOCK | SOCK_CLOEXEC);
    if (fdCon < 0) {
        const auto savedErrno = errno;
        LOG_SYSERROR << "accept4_";
        switch (savedErrno)
        {
        case EAGAIN:
        case ECONNABORTED:
        case EINTR:
        case EPROTO:
        case EPERM:
        case EMFILE:
            break;
        case EBADF:
        case EFAULT:
        case EINVAL:
        case ENOBUFS:
        case ENOMEM:
        case ENOTSOCK:
        case EOPNOTSUPP:
            LOG_FATAL << "unexpected error of accept(): " << savedErrno;
            break;
        default:
            LOG_FATAL << "unknown error of accept(): " << savedErrno;
            break;
        }
    }
    return fdCon;
}

void Sock::bind(int fd, const struct sockaddr *addr) {
    if (Wrap::bind_(fd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in))) < 0) {
        LOG_SYSFATAL << "bind_";
    }
}

int Sock::connect(int fd, const struct sockaddr *addr) {
    return Wrap::connect_(fd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in)));
}

int Sock::create_nonblock_socket(sa_family_t family) {
    const auto fd = Wrap::socket_(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
    if (fd < 0) {
        LOG_SYSFATAL << "socket_";
    }
    return fd;
}

int Sock::socket_error(int fd) {
    int val = 0;
    auto length = static_cast<socklen_t>(sizeof(val));
    return Wrap::getsockopt_(fd, SOL_SOCKET, SO_ERROR, &val, &length) < 0 ? errno : val;
}

void Sock::IP_port_to_network(struct sockaddr_in &addr, const char *ip, uint16_t port) {
    addr.sin_family = AF_INET;
    addr.sin_port = Endian::host_to_network(port);
    if (Wrap::inet_pton_(AF_INET, ip, &addr.sin_addr) < 0) {
        LOG_SYSERROR << "inet_pton_";
    }
}

bool Sock::is_connect_to_self(int fd) {
    const auto local = local_addr(fd);
    const auto peer = peer_addr(fd);
    if (local.sin_family == AF_INET) {
        const auto *const laddr = reinterpret_cast<const struct sockaddr_in *>(&local);
        const auto *const paddr = reinterpret_cast<const struct sockaddr_in *>(&peer);
        return laddr->sin_port == paddr->sin_port && 
               laddr->sin_addr.s_addr && paddr->sin_addr.s_addr;
    }
    return false;
}

struct sockaddr_in Sock::local_addr(int fd) {
    struct sockaddr_in addr {};
    auto length = static_cast<socklen_t>(sizeof(addr));
    if (Wrap::getsockname_(fd, sockaddr_cast(&addr), &length) < 0) {
        LOG_SYSERROR << "getsockname_";
    }
    return addr;
}

void Sock::network_to_IP(char *buffer, size_t size, const struct sockaddr *addr) {
    switch (addr->sa_family)
    {
    case AF_INET:
        assert(size >= INET_ADDRSTRLEN);
        Wrap::inet_ntop_(AF_INET, &sockaddr_in_cast(addr)->sin_addr, buffer, static_cast<socklen_t>(size));
        break;
    
    default:
        break;
    }
}

void Sock::network_to_IP_port(char *buffer, size_t size, const struct sockaddr *addr) {
    network_to_IP(buffer, size, addr);
    const auto length = ::strlen(buffer);
    assert(size > length);
    ::snprintf(buffer + length, size - length, ":%u", 
               Endian::network_to_host(sockaddr_in_cast(addr)->sin_port));
}

struct sockaddr_in Sock::peer_addr(int fd) {
    struct sockaddr_in addr {};
    auto length = static_cast<socklen_t>(sizeof(addr));
    if (Wrap::getpeername_(fd, sockaddr_cast(&addr), &length) < 0) {
        LOG_SYSERROR << "getoptname_";
    }
    return addr;
}

int Sock::set_socket_option(int fd, int name, int opt) {
    return Wrap::setsockopt_(fd, SOL_SOCKET, name, &opt, static_cast<socklen_t>(sizeof(opt)));
}
